/*
 * @Author: czy
 * @Date: 2021-05-19 21:29:37
 * @LastEditTime: 2021-06-03 23:27:48
 */
#include "const.h"
#include "string.h"
#include "vsprintf.h"

#include "dev/hardware/uart.h"
#include "dev/hardware/vga.h"

#include "api/system.h"

static int history_command_pointer = -1;
static char *history_commands[30];
static int history_command_count;

typedef struct
{
    char *head;
    char *tail;
    char *current;
} instruction;

/**
 * @description: Move the cursor left/right several bits.
 * @param offset    Offset.
 * @param fd        Equipment number, -1 for uart, 1 for vga, 0 for uart and vga.
 */
static void cursor_offset(int offset, int fd)
{
    if (fd >= 0)
    {
        vga_offset(offset);
    }
    if (fd <= 0)
    {
        uart_offset(offset);
    }
}

/**
 * @description: Insert a character at the cursor of the current instruction.
 * @param ins   Instruction.
 * @param ch    Character.
 * @param fd    Equipment number, -1 for uart, 1 for vga, 0 for uart and vga.
 */
static void insert(instruction *ins, char ch, int fd)
{
    if (ch >= 32 && ch <= 126)
    {
        strinsert(ins->head, ins->current - ins->head, ch);
        write(ins->current, fd);
        cursor_offset(-1 * (strlen(ins->current) - 1), fd);
        ins->tail++;
        ins->current++;
    }
}

/**
 * @description: Delete a character at the cursor of the current instruction.
 * @param ins   Instruction.
 * @param fd    Equipment number, -1 for uart, 1 for vga, 0 for uart and vga.
 */
static void delete (instruction *ins, int fd)
{
    if (ins->current > ins->head)
    {
        putc(127, fd);
        strdelete(ins->head, ins->current - ins->head - 1);
        ins->current--;
        char temp[100] = "";
        sprintf(temp, "%s \b", ins->current);
        write(temp, fd);
        cursor_offset(-1 * strlen(ins->current), fd);
        ins->tail--;
    }
}

/**
 * @description: Cursor movement.
 * @param ins       Instruction.
 * @param direction LEFT or RIGHT.
 * @param fd    Equipment number, -1 for uart, 1 for vga, 0 for uart and vga.
 */
static void move(instruction *ins, int direction, int fd)
{
    if (direction == LEFT && (ins->current > ins->head))
    {
        ins->current--;
        cursor_offset(-1, fd);
    }
    if (direction == RIGHT && (ins->current < ins->tail))
    {
        ins->current++;
        cursor_offset(1, fd);
    }
}

/**
 * @description: Complete the current instruction.
 * @param ins   Instruction.
 * @param fd    Equipment number, -1 for uart, 1 for vga, 0 for uart and vga.
 */
static int complete(instruction *ins, int length, char *names[], const char *promrt, int fd)
{
    if (ins->head == NULL || ins->current != ins->tail)
    {
        return -1;
    }
    int num, count = 0;
    char *words[WORD_SIZE];
    int match[WORD_SIZE];
    char word[20];
    char temp[80];
    char buf[100] = "";
    strcpy(temp, ins->head);
    deblank(temp);
    num = split(words, temp, " ");
    strcpy(word, words[num - 1]);
    for (int i = 0; i < length; i++)
    {
        if (strindex(word, names[i]) == 0)
        {
            match[count++] = i;
        }
    }
    if (count == 1)
    {
        int len = strlen(word);
        write(names[match[0]] + len, fd);
        strcat(ins->head, names[match[0]] + len);
        ins->current += (strlen(names[match[0]]) - len);
        ins->tail = ins->current;
        return match[0];
    }
    else if (count > 1)
    {
        putc('\n', fd);
        for (int i = 0; i < count; i++)
        {
            sprintf(buf, "%s\t", names[match[i]]);
            write(buf, fd);
            strzero(buf, 100);
        }
        sprintf(buf, "\n%s%s", promrt, ins->head);
        write(buf, fd);
        return -1;
    }
}

/**
 * @description: Replace the current instruction.
 * @param ins   Instruction.
 * @param str   String.
 * @param fd    Equipment number, -1 for uart, 1 for vga, 0 for uart and vga.
 */
static void replace(instruction *ins, char *str, int fd)
{
    for (int i = 0; i < ins->tail - ins->current; i++)
    {
        putc(' ', fd);
    }
    for (int i = 0; i < ins->tail - ins->head; i++)
    {
        putc(127, fd);
    }
    if (*str == '\0')
    {
        strzero(ins->head, 2);
    }
    else
    {
        strcpy(ins->head, str);
    }
    write(ins->head, fd);
    ins->tail = ins->head + strlen(str);
    ins->current = ins->tail;
}

/**
 * @description: Get the history command.
 * @param ins       Instruction.
 * @param direction UP or DOWN.
 * @param fd        Equipment number, -1 for uart, 1 for vga, 0 for uart and vga.
 */
static void history(instruction *ins, int direction, int fd)
{
    char temp[1000] = "";
    env("history", temp);
    history_command_count = split(history_commands, temp, ";");
    if (history_command_pointer < 0)
    {
        history_command_pointer = history_command_count;
    }
    // DOWN
    if (direction == FALSE && history_command_pointer <= history_command_count)
    {
        if (history_command_pointer < history_command_count - 1)
        {
            replace(ins, history_commands[++history_command_pointer], fd);
        }
        else
        {
            replace(ins, "", fd);
        }
    }
    // UP
    if (direction == TRUE && history_command_pointer > 0)
    {
        replace(ins, history_commands[--history_command_pointer], fd);
    }
}

/**
 * @description: Submit the current instruction to the shell.
 * @param ins   Instruction.
 * @param fd    Equipment number, -1 for uart, 1 for vga, 0 for uart and vga.
 */
static char *submit(instruction *ins, int fd)
{
    deblank(ins->head);
    if (*(ins->head) != '\0')
    {
        set("history", ins->head);
    }
    putc('\n', fd);
    history_command_pointer = -1;
    return ins->head;
}

/**
 * @description: Decode the input character.
 * @param ch    Input character.
 * @param fd    Equipment number, -1 for uart, 1 for vga, 0 for uart and vga.
 * @return Decoded character.
 */
static int decode(char ch, int fd)
{
    if (ch == 27)
    {
        if (getc(fd) == 91)
        {
            switch (getc(fd))
            {
            case 65:
                return UP_DIRECTION;
            case 66:
                return DOWN_DIRECTION;
            case 67:
                return RIGHT_DIRECTION;
            case 68:
                return LEFT_DIRECTION;
            default:
                return 0;
            }
        }
    }
    return ch;
}

/**
 * @description: Read a line from the input device.
 * @param str       The first address of the input string.
 * @param promrt    Input prompt.
 * @param fd        Equipment number, -1 for uart, 1 for vga, 0 for uart and vga.
 * @return The first address of the input string.
 */
char *read(char *str, const char *promrt, int fd)
{
    char ch;
    instruction ins = {str, str, str};
    char *commands[30];
    char temp[300] = "";
    env("command", temp);
    int num = split(commands, temp, ";");
    while (TRUE)
    {
        ch = getc(fd);
        switch (decode(ch, fd))
        {
        case '\t':
            complete(&ins, num, commands, promrt, fd);
            break;
        case 127:
            delete (&ins, fd);
            break;
        case LEFT_DIRECTION:
            move(&ins, LEFT, fd);
            break;
        case RIGHT_DIRECTION:
            move(&ins, RIGHT, fd);
            break;
        case UP_DIRECTION:
            history(&ins, TRUE, fd);
            break;
        case DOWN_DIRECTION:
            history(&ins, FALSE, fd);
            break;
        case '\r':
            return submit(&ins, fd);
        default:
            insert(&ins, ch, fd);
            break;
        }
    }
}
